﻿#region License

// PentaBiz - Sustainable Software Development Framework Copyright (C) 2013 Zoltán Csizmazia
// 
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

#endregion

using System;
using System.AddIn.Contract;
using System.AddIn.Pipeline;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;

namespace PentaBiz.Studio.PluginHost
{
    internal class PluginHost : MarshalByRefObject, IPluginHost
    {
        private readonly string _pluginDirectory;
        private readonly string _pluginName;

        public PluginHost(string pluginDirectory, string pluginName)
        {
            _pluginDirectory = pluginDirectory;
            _pluginName = pluginName;
        }

        public PluginHost()
        {
        }

        internal static Assembly PluginAssembly { get; set; }


        public PluginTypeSelector[] GetPluginServiceTypeSelectors()
        {
            Console.WriteLine("GetPluginServiceTypeSelectors");

            var assemblyName = _pluginName;
            var q = from t in PluginAssembly.GetExportedTypes()
                    where t.IsSubclassOf(typeof (PluginService))
                    select new PluginTypeSelector
                        {
                            PluginAssemblyName = assemblyName,
                            PluginTypeName = t.FullName,
                        };

            var arr = q.ToArray();

            Console.WriteLine("Found {0} PluginServices", arr.Length);
            return arr;
        }

        public PluginTypeSelector[] GetPluginCommandTypeSelectors()
        {
            Console.WriteLine("GetPluginCommandTypeSelectors");

            var assemblyName = _pluginName;


            var q = from t in PluginAssembly.GetExportedTypes()
                    where t.IsSubclassOf(typeof (PluginCommand))
                    select new PluginTypeSelector
                        {
                            PluginAssemblyName = assemblyName,
                            PluginTypeName = t.FullName,
                        };

            var arr = q.ToArray();

            Console.WriteLine("Found {0} PluginCommands", arr.Length);

            return arr;
        }

        public PluginTypeSelector[] GetPluginControlTypeSelectors()
        {
            Console.WriteLine("GetPluginControlTypeSelectors");

            var assemblyName = _pluginName;
            var q = from t in PluginAssembly.GetExportedTypes()
                    where t.IsSubclassOf(typeof (PluginControl))
                    select new PluginTypeSelector
                        {
                            PluginAssemblyName = assemblyName,
                            PluginTypeName = t.FullName,
                        };

            var arr = q.ToArray();

            Console.WriteLine("Found {0} PluginControls", arr.Length);

            return arr;
        }

        private void SafeInvokeOnTerminatingCallback()
        {
            IPluginHostEventSubscriber[] a;
            lock (_pluginHostEventSubscribers)
            {
                a = _pluginHostEventSubscribers.ToArray();
            }
            foreach (var pluginCommandEventSubscriber in a)
            {
                try
                {
                    pluginCommandEventSubscriber.OnTerminatingCallback();
                }
                catch (RemotingException)
                {
                    lock (_pluginHostEventSubscribers)
                    {
                        _pluginHostEventSubscribers.Remove(pluginCommandEventSubscriber);
                    }
                }
            }
        }

        private static IPluginCommand CreatePluginCommandOnUIThread(PluginTypeSelector pluginTypeSelector)
        {
            ObjectHandle objectHandle = Activator.CreateInstance(pluginTypeSelector.PluginAssemblyName,
                                                                 pluginTypeSelector.PluginTypeName);
            if (objectHandle == null) throw new InvalidOperationException("Activator.CreateInstance() returned null");

            object obj = objectHandle.Unwrap();
            if (obj == null) throw new InvalidOperationException("Unwrap() returned null");

            var pluginService = obj as IPluginCommand;
            if (pluginService == null)
            {
                string message = string.Format("Object of type {0} cannot be loaded as plugin service " +
                                               "because it does not derive from IPluginCommand",
                                               pluginTypeSelector.PluginTypeName);
                throw new InvalidOperationException(message);
            }

            return pluginService;
        }

        private static IPluginService CreatePluginServiceOnUIThread(PluginTypeSelector pluginTypeSelector)
        {
            ObjectHandle objectHandle = Activator.CreateInstance(pluginTypeSelector.PluginAssemblyName,
                                                                 pluginTypeSelector.PluginTypeName);
            if (objectHandle == null) throw new InvalidOperationException("Activator.CreateInstance() returned null");

            object obj = objectHandle.Unwrap();
            if (obj == null) throw new InvalidOperationException("Unwrap() returned null");

            var pluginService = obj as IPluginService;
            if (pluginService == null)
            {
                string message = string.Format("Object of type {0} cannot be loaded as plugin service " +
                                               "because it does not derive from IPluginService",
                                               pluginTypeSelector.PluginTypeName);
                throw new InvalidOperationException(message);
            }

            return pluginService;
        }

        private static INativeHandleContract CreatePluginControlOnUIThread(PluginTypeSelector pluginTypeSelector)
        {
            ObjectHandle objectHandle = Activator.CreateInstance(pluginTypeSelector.PluginAssemblyName,
                                                                 pluginTypeSelector.PluginTypeName);
            if (objectHandle == null) throw new InvalidOperationException("Activator.CreateInstance() returned null");

            object obj = objectHandle.Unwrap();
            if (obj == null) throw new InvalidOperationException("Unwrap() returned null");

            var pluginControl = obj as PluginControl;
            if (pluginControl == null)
            {
                string message = string.Format("Object of type {0} cannot be loaded as plugin control " +
                                               "because it does not derive from PluginControl",
                                               pluginTypeSelector.PluginTypeName);
                throw new InvalidOperationException(message);
            }


            INativeHandleContract contract = FrameworkElementAdapters.ViewToContractAdapter(pluginControl);
            return contract;
        }

        #region IPluginLoader Members

        private List<IPluginHostEventSubscriber> _pluginHostEventSubscribers = new List<IPluginHostEventSubscriber>();

        public void RegisterEventSubscriber(IPluginHostEventSubscriber pluginServiceEventSubscriber)
        {
            lock (_pluginHostEventSubscribers)
            {
                _pluginHostEventSubscribers.Add(pluginServiceEventSubscriber);
            }
        }

        public void UnregisterEventSubscriber(IPluginHostEventSubscriber pluginHostEventSubscriber)
        {
            lock (_pluginHostEventSubscribers)
            {
                _pluginHostEventSubscribers.Remove(pluginHostEventSubscriber);
            }
        }


        public INativeHandleContract LoadPluginControl(PluginTypeSelector pluginTypeSelector)
        {
            if (pluginTypeSelector == null) throw new ArgumentNullException("pluginTypeSelector");


            Console.WriteLine("Loading plugin control {0},{1}",
                              pluginTypeSelector.PluginAssemblyName,
                              pluginTypeSelector.PluginTypeName);

            try
            {
                Func<PluginTypeSelector, INativeHandleContract> createOnUiThread = CreatePluginControlOnUIThread;
                var contract = (INativeHandleContract) Program.Dispatcher.Invoke(createOnUiThread, pluginTypeSelector);
                var insulator = new NativeHandleContractInsulator(contract);
                return insulator;
            }
            catch (Exception ex)
            {
                string message = String.Format("Error loading plugin control type '{0}' from assembly '{1}'. {2}",
                                               pluginTypeSelector.PluginTypeName,
                                               pluginTypeSelector.PluginAssemblyName, ex.Message);

                throw new ApplicationException(message, ex);
            }
        }

        public IPluginService LoadPluginService(PluginTypeSelector pluginTypeSelector)
        {
            if (pluginTypeSelector == null) throw new ArgumentNullException("pluginTypeSelector");


            Console.WriteLine("Loading plugin service {0},{1}",
                              pluginTypeSelector.PluginAssemblyName,
                              pluginTypeSelector.PluginTypeName);

            try
            {
                Func<PluginTypeSelector, IPluginService> createOnUiThread = CreatePluginServiceOnUIThread;
                var pluginService = (IPluginService) Program.Dispatcher.Invoke(createOnUiThread, pluginTypeSelector);

                return pluginService;
            }
            catch (Exception ex)
            {
                string message = String.Format("Error loading plugin service type '{0}' from assembly '{1}'. {2}",
                                               pluginTypeSelector.PluginTypeName,
                                               pluginTypeSelector.PluginAssemblyName, ex.Message);

                throw new ApplicationException(message, ex);
            }
        }

        public IPluginCommand LoadPluginCommand(PluginTypeSelector pluginTypeSelector)
        {
            if (pluginTypeSelector == null) throw new ArgumentNullException("pluginTypeSelector");


            Console.WriteLine("Loading plugin command {0},{1}",
                              pluginTypeSelector.PluginAssemblyName,
                              pluginTypeSelector.PluginTypeName);

            try
            {
                Func<PluginTypeSelector, IPluginCommand> createOnUiThread = CreatePluginCommandOnUIThread;
                var pluginService = (IPluginCommand) Program.Dispatcher.Invoke(createOnUiThread, pluginTypeSelector);

                return pluginService;
            }
            catch (Exception ex)
            {
                string message = String.Format("Error loading plugin command type '{0}' from assembly '{1}'. {2}",
                                               pluginTypeSelector.PluginTypeName,
                                               pluginTypeSelector.PluginAssemblyName, ex.Message);

                throw new ApplicationException(message, ex);
            }
        }

        public void Terminate()
        {
            SafeInvokeOnTerminatingCallback();
            Program.Dispatcher.InvokeShutdown();
        }

        #endregion
    }
}